package rolesystem.jade.agents.carry.carrier;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.Timer;

import rolesystem.agentlogic.managers.ContactManager;
import rolesystem.agentlogic.managers.MovementManager;
import rolesystem.agentlogic.parameters.ConfigurationParameters;
import rolesystem.core.RoleException;
import rolesystem.core.RoleRegistration;
import rolesystem.core.RoleSystem;
import rolesystem.jade.agents.SubjectLayer;
import rolesystem.middle.managers.BooleanManager;
import rolesystem.middle.managers.CommunicationManager;
import rolesystem.middle.managers.CounterManager;
import rolesystem.middle.managers.DataOutputManager;
import rolesystem.middle.managers.EnvironmentManager;
import rolesystem.middle.managers.KnownEnvironmentManager;
import rolesystem.middle.utilityclasses.AgentMap;
import rolesystem.middle.utilityclasses.PositionSq;
import rolesystem.roles.carry.*;

/**
 * This is a subject layer that can be used by agents in a role system.
 */
public class ExplorerSL implements SubjectLayer
{		
	/**
	 * Variable to reset the agent only on time if no further changes are made.
	 */
	private boolean RESET_LOCAL=true;
	
	/**
	 * The identifier of the agent
	 */
	private int id;
	
	/**
	 * Reference to the Rolesystem
	 */
	private RoleSystem roleSystem=null;
	
	/**
	 * Timer to end the simulation if nothing happens for n time
	 */
	private static Timer end_timer;
	
	/**
	 * Timer to save the data periodically
	 */
	private static Timer data_timer;
	
	/**
	 * Manage the contact (with other agents) phase 
	 */
	private ContactManager contactManager;
	
	/**
	 * Manage the agent movements in the environment 
	 */
	private MovementManager movementManager;
	
	private CommunicationManager communicationManager;
	
	static
	{	
		for(int i=0;i<ConfigurationParameters.counterNames.length;i++)
			CounterManager.add(ConfigurationParameters.counterNames[i]);
		
		for(int i=0;i<ConfigurationParameters.boolNames.length;i++)
			BooleanManager.add(ConfigurationParameters.boolNames[i],ConfigurationParameters.boolInitValues[i]);
		
		
		ActionListener taskPerformer = new ActionListener() 
		{
			public void actionPerformed(ActionEvent evt) 
			{
				BooleanManager.set(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL, checkSimulationEnd());
		    }
		};
		 
		end_timer= new Timer(ConfigurationParameters.SIMULATION_TIME, taskPerformer);
		
		ActionListener dataTaskPerformer = new ActionListener() 
		{
			public void actionPerformed(ActionEvent evt) 
			{
				DataOutputManager.periodicAggregateDataToFile();
			}
		};
		
		data_timer=new Timer(ConfigurationParameters.DATA_SAVE_INTERVAL, dataTaskPerformer);
		data_timer.setRepeats(true);
		data_timer.stop();
	}
	
	/**
	 * Agent constructor 
	 * 
	 * @param  x_start  the position in witch the agent starts - X
	 * @param  y_start  the position in witch the agent starts - Y
	 * @param carry_weight the weich that the agent is able to carry
	 */
	public ExplorerSL(int x_start,int y_start)
		{
			this.id=CounterManager.getAndInc(ConfigurationParameters.ID_COUNTER);
			
			BooleanManager.add(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(id),false);
			BooleanManager.add(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id),false);
			communicationManager=new CommunicationManager();
			contactManager= new ContactManager(this.id,communicationManager);
			EnvironmentManager.resetText();
			movementManager= new MovementManager(this.id,x_start,y_start,contactManager);
			
			System.out.println(id + " Carrier - SubjectLayer initialized");
		}
	
	/**
	 * Put the agent to sleep for a number of milliseconds. 
	 * 
	 * @param sleepTime Number of ms that the agent will sleep 
	 * 
	 */	
	private void sleepAgent(int sleepTime) 
	{	
		try
		{
			Thread.sleep(sleepTime);
		} catch(InterruptedException ie) {}
	}
	
	public static boolean checkSimulationEnd()
	{
		boolean check=true;
		
		for(int i=0;i<CounterManager.get(ConfigurationParameters.ID_COUNTER);i++)
		{
			check= check && BooleanManager.get(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(i));
		}	
		
		return check;
	}
	
	
	
	/**
	 * The actions that need to be done to reset the agent and the environment. 
	 * @throws RoleException 
	 * 
	 */	
	private synchronized void resetAgent(RoleRegistration registration) throws RoleException
	{	
		CounterManager.inc(ConfigurationParameters.RESET_COUNTER);
		this.RESET_LOCAL=false;
		movementManager.reset(new PositionSq((int)(Math.random()*(EnvironmentManager.ENV_MAX_X-1)),
				(int)(Math.random()*(EnvironmentManager.ENV_MAX_Y-1))
				));  
		
		contactManager.clearOutstandingMessages(registration);
		
		DataOutputManager.printString(id + " Agent Reset");
		
		if(CounterManager.equals(ConfigurationParameters.RESET_COUNTER, ConfigurationParameters.ID_COUNTER))
		{
			EnvironmentManager.reset();
			EnvironmentManager.resetText();
			EnvironmentManager.updateVisualization();
			
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_BOOL,false);
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_INV_BOOL,false); 
			
			CounterManager.reset(ConfigurationParameters.TIMER_END_COUNTER);
			DataOutputManager.dataSim.clearAgentData();
			DataOutputManager.printString("Agent count id: " + CounterManager.get(ConfigurationParameters.ID_COUNTER));	
			DataOutputManager.dataSim.createAgentData(CounterManager.get(ConfigurationParameters.ID_COUNTER));
			
			for(int i=0;i<CounterManager.get(ConfigurationParameters.ID_COUNTER);i++)
				BooleanManager.set(ConfigurationParameters.END_CHECK_BASE_BOOL + String.valueOf(i),false);
			BooleanManager.set(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL,checkSimulationEnd());
			
			KnownEnvironmentManager.ResetMergedKnownEnvironments();
			
			BooleanManager.set(ConfigurationParameters.RESET_BOOL,false);
			CounterManager.reset(ConfigurationParameters.RESET_COUNTER);
			
			DataOutputManager.printString("## Environment Reset");
			movementManager.drawVisualization();
		}	
	}
	
	
	
	
	
	/**
	 * Reset the agent and the environment. 
	 * @throws RoleException 
	 * 
	 */	
	private void resetAll(RoleRegistration registration) throws RoleException
	{	
		if(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL))
		{
			movementManager.reachObject(new PositionSq(1,1));
			
			BooleanManager.set(ConfigurationParameters.STOP_BOOL,true);
			
			CounterManager.inc(ConfigurationParameters.TIMER_END_COUNTER);
			
			if(data_timer.isRunning())
				data_timer.stop();	
			
			if(end_timer.isRunning())
				end_timer.stop();
			
			if(CounterManager.equals(ConfigurationParameters.TIMER_END_COUNTER, ConfigurationParameters.ID_COUNTER))
			{
				DataOutputManager.SimulationStop();
				DataOutputManager.aggregateDataToFile();
				DataOutputManager.endsimulation();
				
				EnvironmentManager.stopSignal();
				
				KnownEnvironmentManager.mergeKnownEnvironments();
				
				movementManager.drawVisualization();
			}	
		}
		
		while(BooleanManager.get(ConfigurationParameters.STOP_BOOL)==true) 
		{
			sleepAgent(100);
			
			if(BooleanManager.get(ConfigurationParameters.RESET_BOOL)==true && this.RESET_LOCAL==true)
				resetAgent(registration);			
		};
		
		RESET_LOCAL=true;
		CounterManager.reset(ConfigurationParameters.RESET_COUNTER);
		
		if(!end_timer.isRunning())
		{	
			KnownEnvironmentManager.CreateKnownEnvironments(CounterManager.get(ConfigurationParameters.ID_COUNTER));
			end_timer.start();
			DataOutputManager.SimulationStart();
		}	
		
		if(!data_timer.isRunning())
			data_timer.start();
		
	}
	
	/**
	 * The actions that need to be done on agent start.
	 *  
	 * @param rSystem A reference to the RoleSystem
	 * @return Reference to the role of the agent in the Role System.
	 */	
	private RoleRegistration start_operations(RoleSystem rSystem) throws RoleException
	{
		RoleRegistration registration;
		
		DataOutputManager.printString(id + " Agent Started");
		
		this.roleSystem=rSystem;
		
		try
		{
			registration=roleSystem.reqRegistration(Explorer.ROLE_ID);
		}
		catch(RoleException re)
		{
			System.err.println(re);
			return null;
		}
		
		DataOutputManager.printString(id + " Registration OK (Carrier)");		
		
		contactManager.setRoleSystem(roleSystem);
		
		CounterManager.inc(ConfigurationParameters.START_COUNTER);
		
		if(CounterManager.get(ConfigurationParameters.START_COUNTER)==ConfigurationParameters.NUMBER_OF_AGENTS_EXPL)
		{		
			DataOutputManager.printString("Agent count id" + CounterManager.get(ConfigurationParameters.ID_COUNTER));	
			DataOutputManager.dataSim.createAgentData(CounterManager.get(ConfigurationParameters.ID_COUNTER));	
			AgentMap.createAgentMap(CounterManager.get(ConfigurationParameters.ID_COUNTER));
			
		}	
			
		while(CounterManager.get(ConfigurationParameters.START_COUNTER)!=ConfigurationParameters.NUMBER_OF_AGENTS_EXPL)
		{
			sleepAgent(100);
		};
		
		movementManager.drawVisualization();
		
		return registration;	
		
		
	}
	
	
	
	/**
	 * The actions that need to be done before exit.
	 *  
	 * @param registration Reference to the role of the agent in the Role System, necessary to communicate with other agents.
	 * @return Reference to the role of the agent in the Role System.
	 */	
	private RoleRegistration exit_operations(RoleRegistration registration) throws RoleException
	{
		movementManager.reachObject(new PositionSq(1,1));
		
		DataOutputManager.printString(id + " EXIT");
		
		CounterManager.inc(ConfigurationParameters.EXIT_COUNTER);
		registration.dismiss();
		
		if(data_timer.isRunning() && BooleanManager.get(ConfigurationParameters.EXIT_BOOL)==true)
			data_timer.stop();	
		
		if(CounterManager.equals(ConfigurationParameters.EXIT_COUNTER, ConfigurationParameters.ID_COUNTER))
		{
			if(data_timer.isRunning())
				data_timer.stop();
			
			DataOutputManager.SimulationStop();
			end_timer.stop();
			
			DataOutputManager.closeAggregateDataFile();
			KnownEnvironmentManager.mergeKnownEnvironments();
			
			movementManager.drawVisualization();
		}	
		
		return registration;	
	
	}
	
	private void manageChangeModeCounter(RoleRegistration registration) throws RoleException
	{
		CounterManager.inc(ConfigurationParameters.CHANGE_MODE_COUNTER);
		BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_INV_BOOL,true);
		contactManager.clearOutstandingMessages(registration);
		
		if(CounterManager.equals(ConfigurationParameters.CHANGE_MODE_COUNTER, ConfigurationParameters.ID_COUNTER))
		{
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_BOOL,false);
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_INV_BOOL,false);
			CounterManager.reset(ConfigurationParameters.CHANGE_MODE_COUNTER);
			DataOutputManager.printString(" Mode Change: Random");
		}
		
		while(BooleanManager.get(ConfigurationParameters.MASTER_BLOCK_INV_BOOL))
		{
			sleepAgent(100);
		}	
	}
	
	private void manageChangeModeCounterInv(RoleRegistration registration) throws RoleException
	{
		CounterManager.inc(ConfigurationParameters.CHANGE_MODE_COUNTER);
		BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_BOOL,true);
		contactManager.clearOutstandingMessages(registration);
		
		if(CounterManager.equals(ConfigurationParameters.CHANGE_MODE_COUNTER, ConfigurationParameters.ID_COUNTER))
		{
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_INV_BOOL,false);
			BooleanManager.set(ConfigurationParameters.MASTER_BLOCK_BOOL,false);
			CounterManager.reset(ConfigurationParameters.CHANGE_MODE_COUNTER);
			DataOutputManager.printString(" Mode Change: Master Slave");
		}
		
		while(BooleanManager.get(ConfigurationParameters.MASTER_BLOCK_BOOL))
		{
			sleepAgent(100);
		}	
	}

	
	/**
	 * Method called by the RoleSystem on startup, contains the code managing the internal functioning of the agent
	 * 
	 * @param rSystem A reference to the RoleSystem
	 * @see RoleSystem
	 */	
	public void go(RoleSystem rSystem)
	{
		RoleRegistration registration;
		
		try
		{
			registration=start_operations(rSystem);
		
			if(registration==null)
				return;
		
			while(BooleanManager.get(ConfigurationParameters.EXIT_BOOL)==false && BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==false)
			{			
				resetAll(registration);  // Check if the GUI commanded a reset
				
				if(ConfigurationParameters.searchType==1)
				{	
					while(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL)==false  && BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==false)
					{
						manageChangeModeCounter(registration);
						
						movementManager.moveRandom(registration);
						
						if(BooleanManager.get(ConfigurationParameters.END_SEARCH_MOVEMENT_BOOL)==true || BooleanManager.get(ConfigurationParameters.EXIT_AGENT_BASE_BOOL + String.valueOf(id))==true)
							continue;
						
						manageChangeModeCounterInv(registration);
						
						if(movementManager.NEW_MASTER_TO_INIT)
						{
							movementManager.NEW_MASTER_TO_INIT=false;
							DataOutputManager.printString(this.id +" I am master (change)");
							
							registration.dismiss();
							registration=roleSystem.reqRegistration(Master.ROLE_ID);
							
							contactManager.announceMaster(registration);
							movementManager.moveMaster(registration);
							
							registration.dismiss();
							registration=roleSystem.reqRegistration(Explorer.ROLE_ID);
							continue;
						}	
						
						if(movementManager.SLAVE_INIT)
						{
							movementManager.SLAVE_INIT=false;
							
							DataOutputManager.printString(this.id +" I am slave");
							contactManager.acknowledgeMaster(registration);
							movementManager.moveSlave(registration);
						}	
					}
				}
				
				if(ConfigurationParameters.searchType==2)
				{	
					boolean master=ContactManager.decideMaster();
					if(master)
					{
						DataOutputManager.printString(this.id +" I am master");
						
						registration.dismiss();
						registration=roleSystem.reqRegistration(Master.ROLE_ID);

						contactManager.announceMaster(registration);
						movementManager.moveMaster(registration);	
					}
					else
					{
						DataOutputManager.printString(this.id +" I am slave");
						contactManager.acknowledgeMaster(registration);
						movementManager.moveSlave(registration);
					}
					
				}
			}	
			
			registration=exit_operations(registration);
		}	
		catch(RoleException re)
		{
			System.err.println(re);
		}
	}
}


